/** @file    WeatherSourceYahooAPI.cpp
 *  @author  Alessandro Polo
 *  @version $Id: WeatherSourceYahooAPI.cpp 2891 2010-08-12 01:58:59Z alex $
 *  @brief
 * File containing methods for the WeatherSourceYahooAPI class.
 * The header for this class can be found in WeatherSourceYahooAPI.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "WeatherSourceYahooAPI.h"

 #include <core/Utilities.h>

 #include <QFile>
 #include <QtXml>


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

WeatherSourceYahooAPI::WeatherSourceYahooAPI()
	: WeatherSourceHttp() {
	Object::setName("WeatherSourceYahooAPI");
	Log.setContext( "WeatherSourceYahooAPI" );
	this->priority = 40;
	this->reliability = 70.0;
	this->xmlUrl = "http://weather.yahooapis.com/forecastrss?w=722102&u=c";
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT WeatherSourceYahooAPI::loadForecast( WeatherInfo* wCurrentInfo, std::vector<WeatherInfo*>* forecast ) {
	FilePath srcFile;
	srcFile.set( this->xmlLocalTemp );
	std::string filename = srcFile.toPathLocal();
	QDomDocument doc("none");
	QFile file(filename.c_str());
	if ( !file.open(QIODevice::ReadOnly) ) {
		Log(LOG_CRITICAL, ":loadForecast() FAILED : Can't open file: %s", filename.c_str() );
		return WRET_ERR_PARAM;
	 }
	if ( !doc.setContent(&file) ) {
		file.close();
		Log(LOG_CRITICAL, ":loadForecast() FAILED : Can't set contents from: %s", filename.c_str() );
		return WRET_ERR_PARAM;
	 }
	file.close();
	QDomElement rss_Node = doc.documentElement();
	if ( rss_Node.isNull() ) {
		Log(LOG_CRITICAL, ":loadForecast() FAILED : No 'rss' node in %s", filename.c_str() );
		return WRET_ERR_INTERNAL;
	 }
	QDomElement channelNode = rss_Node.namedItem("channel").toElement();
	if ( channelNode.isNull() ) {
		Log(LOG_CRITICAL, ":loadForecast() FAILED : No 'channel' node in %s", filename.c_str() );
		return WRET_ERR_INTERNAL;
	 }
	QDomElement itemNode = channelNode.namedItem("item").toElement();
	int64 dataTimestamp = FileSystem::getTimeStampOf(filename);
	if ( wCurrentInfo != NULL ) {
/*
<yweather:location city="Rovereto" region="TT"   country="Italy"/>
<yweather:units temperature="C" distance="km" pressure="mb" speed="km/h"/>
<yweather:wind chill="29"   direction="240"   speed="14.48" />
<yweather:atmosphere humidity="23"  visibility="9.99"  pressure="982.05"  rising="2" />
<yweather:astronomy sunrise="5:52 am"   sunset="8:53 pm"/>
<item>
  <title>Conditions for Rovereto, IT at 1:45 pm CEST</title>
  <geo:lat>45.88</geo:lat>
  <geo:long>11.04</geo:long>
  <link>http://us.rd.yahoo.com/dailynews/rss/weather/Rovereto__IT/*http://weather.yahoo.com/forecast/ITXX0208_c.html</link>
  <pubDate>Sun, 25 Jul 2010 1:45 pm CEST</pubDate>
  <yweather:condition  text="Partly Cloudy"  code="30"  temp="29"  date="Sun, 25 Jul 2010 1:45 pm CEST" />
  <description><![CDATA[ ]]></description>
  <yweather:forecast day="Sun" date="25 Jul 2010" low="19" high="28" text="Partly Cloudy" code="30" />
  <yweather:forecast day="Mon" date="26 Jul 2010" low="19" high="25" text="PM Thunderstorms" code="38" />
  <guid isPermaLink="false">ITXX0208_2010_07_25_13_45_CEST</guid>
</item>
*/
		Log(LOG_INFO, ":loadForecast() : Parsing Current conditions.." );
		configureWeatherInfo(wCurrentInfo, dataTimestamp);

		QDomElement windNode = channelNode.namedItem("yweather:wind").toElement();
		if ( !windNode.isNull() ) {
			if ( windNode.hasAttribute("chill") )
				wCurrentInfo->setWindChill( windNode.attribute("chill").toInt() );
			if ( windNode.hasAttribute("direction") )
				wCurrentInfo->setWindDirection( windNode.attribute("direction").toInt() );
			if ( windNode.hasAttribute("speed") )
				wCurrentInfo->setWindSpeed( windNode.attribute("speed").toDouble() );
		 }
		QDomElement atmosphereNode = channelNode.namedItem("yweather:atmosphere").toElement();
		if ( !atmosphereNode.isNull() ) {
			if ( atmosphereNode.hasAttribute("humidity") )
				wCurrentInfo->setAtmosphereHumidity( atmosphereNode.attribute("humidity").toInt() );
			if ( atmosphereNode.hasAttribute("visibility") )
				wCurrentInfo->setAtmosphereVisibility( atmosphereNode.attribute("visibility").toDouble() );
			if ( atmosphereNode.hasAttribute("pressure") )
				wCurrentInfo->setAtmospherePressure( atmosphereNode.attribute("pressure").toDouble() );
			if ( atmosphereNode.hasAttribute("rising") )
				wCurrentInfo->setAtmosphereRising( atmosphereNode.attribute("rising").toInt() );
		 }
		QDomElement astronomyNode = channelNode.namedItem("yweather:astronomy").toElement();
		if ( !astronomyNode.isNull() ) {
			if ( astronomyNode.hasAttribute("sunrise") )
				wCurrentInfo->setSunRise( parseDayTime( astronomyNode.attribute("sunrise").toLatin1().data() ) );
			if ( astronomyNode.hasAttribute("sunset") )
				wCurrentInfo->setSunSet( parseDayTime( astronomyNode.attribute("sunset").toLatin1().data() ) );
		 }

		if ( !itemNode.isNull() ) {
			QDomElement conditionNode = itemNode.namedItem("yweather:condition").toElement();
			if ( !conditionNode.isNull() ) {
				if ( conditionNode.hasAttribute("date") ) {
					wCurrentInfo->setPeriodName( conditionNode.attribute("date").left(3).toLatin1().data() );
					wCurrentInfo->setPeriodDay( parseDateTime( conditionNode.attribute("date").toLatin1().data() ) );
				 }
				if ( conditionNode.hasAttribute("code") ) {
					int code, condition, xtra;
					code = conditionNode.attribute("code").toInt();
					parseWeatherCode(code, condition, xtra);
					if ( condition == 0 )
						Log(LOG_WARNING, ":loadForecast() Unknown condition '%d'", code );
					wCurrentInfo->setCondition(condition, xtra);
				 }
				if ( conditionNode.hasAttribute("text") )
					wCurrentInfo->setConditionKey( conditionNode.attribute("text").toLatin1().data() );
				if ( conditionNode.hasAttribute("description") )
					wCurrentInfo->setConditionSummary( conditionNode.attribute("description").toLatin1().data() );
				if ( conditionNode.hasAttribute("temp") )
					wCurrentInfo->setTemperature( conditionNode.attribute("temp").toInt() );
			}
		 }
		else {
			Log(LOG_WARNING, ":loadForecast() FAILED : No 'item' node in %s", filename.c_str() );
		 }
	}

	if ( forecast != NULL ) {
		if ( !itemNode.isNull() ) {
			QDomElement pubDateNode = itemNode.namedItem("pubDate").toElement();
			if ( !pubDateNode.isNull() )
				dataTimestamp = parseDateTime( pubDateNode.text().toLatin1().data() );
			if ( dataTimestamp == 0 )
				dataTimestamp = Utilities::std_time();
			Log(LOG_INFO, ":loadForecast() : Parsing Forecast conditions.." );
			QDomNodeList forecastList = itemNode.elementsByTagName( "yweather:forecast" );
			for(int j=0; j<forecastList.size(); j++) {
				QDomElement cForecastNode = forecastList.item(j).toElement();
				WeatherInfo* wInfo = new WeatherInfo();
				configureWeatherInfo(wInfo, dataTimestamp);
				if ( cForecastNode.hasAttribute("day") )
					wInfo->setPeriodName( cForecastNode.attribute("day").toLatin1().data() );
				if ( cForecastNode.hasAttribute("date") )
					wInfo->setPeriodDay( parseDateTime( cForecastNode.attribute("date").toLatin1().data() ) );
				if ( cForecastNode.hasAttribute("high") )
					wInfo->setTemperatureHighest( cForecastNode.attribute("high").toInt() );
				if ( cForecastNode.hasAttribute("low") )
					wInfo->setTemperatureLowest( cForecastNode.attribute("low").toInt() );
				if ( cForecastNode.hasAttribute("text") )
					wInfo->setConditionKey( cForecastNode.attribute("text").toLatin1().data() );
				if ( cForecastNode.hasAttribute("code") ) {
					int code, condition, xtra; code = condition = xtra = 0;
					code = cForecastNode.attribute("code").toInt();
					parseWeatherCode(code, condition, xtra);
					if ( condition == 0 )
						Log(LOG_WARNING, ":loadForecast() Unknown condition '%d'", code );
					wInfo->setCondition(condition, xtra);
				 }
				Log(LOG_VERBOSE, ":loadForecast() : Adding Forecast-item.." );
				forecast->push_back(wInfo);
			 }
		 }
		else {
			Log(LOG_WARNING, ":loadForecast() FAILED : No 'item' node in %s", filename.c_str() );
		 }
	 }
	return WRET_OK;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

long WeatherSourceYahooAPI::parseDayTime( const std::string& text ) const {
	QTime time = QTime::fromString( text.c_str(), "h:mm ap" );
	QTime midnight(0, 0, 0);
	return midnight.secsTo(time);
 }

int64 WeatherSourceYahooAPI::parseDateTime( const std::string& text ) const {
	QDateTime dt = QDateTime::fromString( text.c_str(), "ddd, d MMM yyyy h:mm ap" );
	return dt.toTime_t();
 }

int64 WeatherSourceYahooAPI::parseDate( const std::string& text ) const {
	QDateTime dt = QDateTime::fromString( text.c_str(), "d MMM yyyy" );
	return dt.toTime_t();
 }


WRESULT WeatherSourceYahooAPI::parseWeatherCode( int code, int& condition, int& xtra ) const {
	switch(code) {
		case 0: // tornado
		case 1: // tropical storm
		case 2: // hurricane
		case 3: // severe thunderstorms
			 {
				condition = (int)WeatherInfo::CONDITION_THUNDERSTORMS;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SEVERE;
				break;
			 }
		case 4: // thunderstorms
			 {
				condition = (int)WeatherInfo::CONDITION_THUNDERSTORMS;
				xtra = (int)WeatherInfo::CONDITION_XTRA_NONE;
				break;
			 }
		case 5: // mixed rain and snow
			 {
				condition = (int)WeatherInfo::CONDITION_RAIN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 6: // mixed rain and sleet
		case 7: // mixed snow and sleet
		case 8: // freezing drizzle
		case 9: // drizzle
		case 10: // freezing rain
		case 11: // showers
		case 12: // showers
		case 13: // snow flurries
		case 14: // light snow showers
		case 15: // blowing snow
		case 16: // snow
			 {
				condition = (int)WeatherInfo::CONDITION_SNOW;
				xtra = (int)WeatherInfo::CONDITION_XTRA_NONE;
				break;
			 }
		case 17: // hail
		case 18: // sleet
		case 19: // dust
		case 20: // foggy
		case 21: // haze
		case 22: // smoky
		case 23: // blustery
		case 24: // windy
		case 25: // cold
		case 26: // cloudy
			 {
				condition = (int)WeatherInfo::CONDITION_CLOUDY;
				xtra = (int)WeatherInfo::CONDITION_XTRA_NONE;
				break;
			 }
		case 27: // mostly cloudy (night)
		case 28: // mostly cloudy (day)
		case 29: // partly cloudy (night)
		case 30: // partly cloudy (day)
			 {
				condition = (int)WeatherInfo::CONDITION_CLOUDY;
				xtra = (int)WeatherInfo::CONDITION_XTRA_PARTLY;
				break;
			 }
		case 31: // clear (night)
		case 32: // sunny
			 {
				condition = (int)WeatherInfo::CONDITION_SUNNY;
				xtra = (int)WeatherInfo::CONDITION_XTRA_NONE;
				break;
			 }
		case 33: // fair (night)
		case 34: // fair (day)
		case 35: // mixed rain and hail
			 {
				condition = (int)WeatherInfo::CONDITION_RAIN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 36: // hot
		case 37: // isolated thunderstorms
		case 38: // scattered thunderstorms
		case 39: // scattered thunderstorms
			 {
				condition = (int)WeatherInfo::CONDITION_THUNDERSTORMS;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 40: // scattered showers
			 {
				condition = (int)WeatherInfo::CONDITION_RAIN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 41: // heavy snow
			 {
				condition = (int)WeatherInfo::CONDITION_SNOW;
				xtra = (int)WeatherInfo::CONDITION_XTRA_HEAVY;
				break;
			 }
		case 42: // scattered snow showers
			 {
				condition = (int)WeatherInfo::CONDITION_SNOW | (int)WeatherInfo::CONDITION_RAIN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 43: // heavy snow
			 {
				condition = (int)WeatherInfo::CONDITION_SNOW;
				xtra = (int)WeatherInfo::CONDITION_XTRA_HEAVY;
				break;
			 }
		case 44: // partly cloudy
			 {
				condition = (int)WeatherInfo::CONDITION_CLOUDY;
				xtra = (int)WeatherInfo::CONDITION_XTRA_PARTLY;
				break;
			 }
		case 45: // thundershowers
			 {
				condition = (int)WeatherInfo::CONDITION_THUNDERSTORMS;
				xtra = (int)WeatherInfo::CONDITION_XTRA_SCATTERED;
				break;
			 }
		case 46: // snow showers
			 {
				condition = (int)WeatherInfo::CONDITION_SNOW | (int)WeatherInfo::CONDITION_RAIN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_HEAVY;
				break;
			 }
		case 47: // isolated thundershowers
			 {
				condition = (int)WeatherInfo::CONDITION_THUNDERSTORMS;
				xtra = (int)WeatherInfo::CONDITION_XTRA_PARTLY;
				break;
			 }
		case 3200:
		default:
			 {
				condition = (int)WeatherInfo::CONDITION_UNKNOWN;
				xtra = (int)WeatherInfo::CONDITION_XTRA_NONE;
			 }
	 }

	return WRET_OK;
 }
